package com.wujunshen.orika;

import com.wujunshen.orika.configurer.OrikaMapperFactoryBuilderConfigurer;
import com.wujunshen.orika.configurer.OrikaMapperFactoryConfigurer;
import com.wujunshen.orika.convert.DateConverter;
import com.wujunshen.orika.convert.ValueConverter;
import com.wujunshen.orika.properties.EnumConvertProperties;
import com.wujunshen.orika.properties.JSONConvertProperties;
import com.wujunshen.orika.properties.OrikaProperties;
import com.wujunshen.orika.utils.OrikaUtils;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory.MapperFactoryBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
 * User:  FrankWoo(吴峻申) <br>
 * Date:  2018/7/30 <br>
 * Time:  23:49 <br>
 * Email: frank_wjs@hotmail.com <br>
 */
@Slf4j
@ConditionalOnProperty(name = "orika.enabled", matchIfMissing = true)
@EnableConfigurationProperties(OrikaProperties.class)
@Configuration
public class OrikaAutoConfiguration {
    /**
     * The configuration properties for Orika.
     */
    @Resource
    private OrikaProperties orikaProperties;

    /**
     * The configurers for {@link MapperFactoryBuilder}.
     */
    @Resource
    private Optional<List<OrikaMapperFactoryBuilderConfigurer>> orikaMapperFactoryBuilderConfigurers;

    /**
     * The configurers for {@link MapperFactory}.
     */
    @Resource
    private Optional<List<OrikaMapperFactoryConfigurer>> orikaMapperFactoryConfigurers;

    /**
     * Creates a {@link MapperFactoryBuilder}.
     *
     * @return a {@link MapperFactoryBuilder}.
     */
    @ConditionalOnMissingBean
    @Bean
    public MapperFactoryBuilder<?, ?> orikaMapperFactoryBuilder() {
        DefaultMapperFactory.Builder orikaMapperFactoryBuilder = new DefaultMapperFactory.Builder();
        log.debug("\nuseBuiltinConverters:{},useAutoMapping:{}," +
                        "mapNulls:{},dumpStateOnException:{}," +
                        "favorExtension:{},captureFieldContext:{}\n",
                orikaProperties.isUseBuiltinConverters(),
                orikaProperties.isUseAutoMapping(),
                orikaProperties.isMapNulls(),
                orikaProperties.isDumpStateOnException(),
                orikaProperties.isFavorExtension(),
                orikaProperties.isCaptureFieldContext());

        orikaMapperFactoryBuilder.useBuiltinConverters(orikaProperties.isUseBuiltinConverters());
        orikaMapperFactoryBuilder.useAutoMapping(orikaProperties.isUseAutoMapping());
        orikaMapperFactoryBuilder.mapNulls(orikaProperties.isMapNulls());
        orikaMapperFactoryBuilder.dumpStateOnException(orikaProperties.isDumpStateOnException());
        orikaMapperFactoryBuilder.favorExtension(orikaProperties.isFavorExtension());
        orikaMapperFactoryBuilder.captureFieldContext(orikaProperties.isCaptureFieldContext());

        orikaMapperFactoryBuilderConfigurers
                .orElseGet(Collections::emptyList)
                .forEach(configurer -> configurer.configure(orikaMapperFactoryBuilder));
        return orikaMapperFactoryBuilder;
    }

    /**
     * Creates a {@link MapperFactory}.
     *
     * @param orikaMapperFactoryBuilder the {@link MapperFactoryBuilder}.
     * @return a {@link MapperFactory}.
     */
    @ConditionalOnMissingBean
    @Bean
    public MapperFactory orikaMapperFactory(MapperFactoryBuilder<?, ?> orikaMapperFactoryBuilder) throws ClassNotFoundException {
        MapperFactory mapperFactory = orikaMapperFactoryBuilder.build();
        orikaMapperFactoryConfigurers
                .orElseGet(Collections::emptyList)
                .forEach(configurer -> configurer.configure(mapperFactory));

        mapperFactory.getConverterFactory().registerConverter(
                orikaProperties.getDateConvert(), new DateConverter());

        mapperFactory.getConverterFactory().registerConverter(
                orikaProperties.getValueConvert(), new ValueConverter<>());

        for (JSONConvertProperties jsonConvertProperties : orikaProperties.getJson()) {
            OrikaUtils.createJsonConvert(mapperFactory,
                    jsonConvertProperties.getConvertName(),
                    Class.forName(jsonConvertProperties.getClassName()));
        }

        for (EnumConvertProperties enumConvertProperties : orikaProperties.getEnumeration()) {
            OrikaUtils.createEnumConvert(mapperFactory,
                    enumConvertProperties.getConvertName(),
                    Class.forName(enumConvertProperties.getClassName()));
        }

        return mapperFactory;
    }
}